package io.debezium.connector.postgresql;

import com.huawei.gaussdb.jdbc.core.TypeInfo;

import java.util.List;
import java.util.Objects;

public class PostgresType {
    public static final PostgresType UNKNOWN =
            new PostgresType(
                    "unknown",
                    -1,
                    Integer.MIN_VALUE,
                    (TypeInfo) null,
                    (List) null,
                    (PostgresType) null,
                    (PostgresType) null);
    private final String name;
    private final int oid;
    private final int jdbcId;
    private final PostgresType parentType;
    private final PostgresType elementType;
    private final TypeInfo typeInfo;
    private final int modifiers;
    private final List<String> enumValues;

    private PostgresType(
            String name,
            int oid,
            int jdbcId,
            TypeInfo typeInfo,
            List<String> enumValues,
            PostgresType parentType,
            PostgresType elementType) {
        this(name, oid, jdbcId, -1, typeInfo, enumValues, parentType, elementType);
    }

    private PostgresType(
            String name,
            int oid,
            int jdbcId,
            int modifiers,
            TypeInfo typeInfo,
            List<String> enumValues,
            PostgresType parentType,
            PostgresType elementType) {
        Objects.requireNonNull(name);
        this.name = name;
        this.oid = oid;
        this.jdbcId = jdbcId;
        this.typeInfo = typeInfo;
        this.parentType = parentType;
        this.elementType = elementType;
        this.modifiers = modifiers;
        this.enumValues = enumValues;
    }

    public boolean isArrayType() {
        return this.elementType != null;
    }

    public boolean isRootType() {
        return this.parentType == null;
    }

    public boolean isEnumType() {
        return this.enumValues != null;
    }

    public String getName() {
        return this.name;
    }

    public int getOid() {
        return this.oid;
    }

    public int getJdbcId() {
        return this.jdbcId;
    }

    public PostgresType getElementType() {
        return this.elementType;
    }

    public PostgresType getParentType() {
        return this.parentType;
    }

    public PostgresType getRootType() {
        PostgresType rootType;
        for (rootType = this; !rootType.isRootType(); rootType = rootType.getParentType()) {}

        return rootType;
    }

    public List<String> getEnumValues() {
        return this.enumValues;
    }

    public int getDefaultLength() {
        if (this.typeInfo == null) {
            return -1;
        } else {
            int size;
            if (this.parentType != null) {
                if (this.modifiers == -1) {
                    return this.parentType.getDefaultLength();
                }

                size = this.typeInfo.getPrecision(this.parentType.getOid(), this.modifiers);
                if (size == 0) {
                    size = this.typeInfo.getDisplaySize(this.parentType.getOid(), this.modifiers);
                }

                if (size != 0 && size != Integer.MAX_VALUE) {
                    return size;
                }
            }

            size = this.typeInfo.getPrecision(this.oid, this.modifiers);
            if (size == 0) {
                size = this.typeInfo.getDisplaySize(this.oid, this.modifiers);
            }

            return size;
        }
    }

    public int getDefaultScale() {
        if (this.typeInfo == null) {
            return -1;
        } else if (this.parentType != null) {
            return this.modifiers == -1
                    ? this.parentType.getDefaultScale()
                    : this.typeInfo.getScale(this.parentType.getOid(), this.modifiers);
        } else {
            return this.typeInfo.getScale(this.oid, this.modifiers);
        }
    }

    public int length(int modifier) {
        if (this.typeInfo == null) {
            return -1;
        } else {
            switch (this.oid) {
                case 1083:
                case 1114:
                case 1184:
                case 1186:
                case 1266:
                    return this.typeInfo.getPrecision(this.oid, modifier);
                default:
                    return modifier;
            }
        }
    }

    public TypeInfo getTypeInfo() {
        return this.typeInfo;
    }

    public int scale(int modifier) {
        if (this.typeInfo == null) {
            return -1;
        } else {
            switch (this.oid) {
                case 1083:
                case 1114:
                case 1184:
                case 1186:
                case 1266:
                    return this.typeInfo.getScale(this.oid, modifier);
                default:
                    return this.getDefaultScale();
            }
        }
    }

    public int hashCode() {
        int result = 1;
        result = 31 * result + this.oid;
        return result;
    }

    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        } else if (obj == null) {
            return false;
        } else if (this.getClass() != obj.getClass()) {
            return false;
        } else {
            PostgresType other = (PostgresType) obj;
            return this.oid == other.oid;
        }
    }

    public String toString() {
        return "PostgresType [name="
                + this.name
                + ", oid="
                + this.oid
                + ", jdbcId="
                + this.jdbcId
                + ", modifiers="
                + this.modifiers
                + ", defaultLength="
                + this.getDefaultLength()
                + ", defaultScale="
                + this.getDefaultScale()
                + ", parentType="
                + this.parentType
                + ", elementType="
                + this.elementType
                + "]";
    }

    public static class Builder {
        private final TypeRegistry typeRegistry;
        private final String name;
        private final int oid;
        private final int jdbcId;
        private final int modifiers;
        private final TypeInfo typeInfo;
        private int parentTypeOid;
        private int elementTypeOid;
        private List<String> enumValues;

        public Builder(
                TypeRegistry typeRegistry,
                String name,
                int oid,
                int jdbcId,
                int modifiers,
                TypeInfo typeInfo) {
            this.typeRegistry = typeRegistry;
            this.name = name;
            this.oid = oid;
            this.jdbcId = jdbcId;
            this.modifiers = modifiers;
            this.typeInfo = typeInfo;
        }

        public Builder parentType(int parentTypeOid) {
            this.parentTypeOid = parentTypeOid;
            return this;
        }

        public boolean hasParentType() {
            return this.parentTypeOid != 0;
        }

        public Builder elementType(int elementTypeOid) {
            this.elementTypeOid = elementTypeOid;
            return this;
        }

        public Builder enumValues(List<String> enumValues) {
            this.enumValues = enumValues;
            return this;
        }

        public PostgresType build() {
            PostgresType parentType = null;
            if (this.hasParentType()) {
                parentType = this.typeRegistry.get(this.parentTypeOid);
            }

            PostgresType elementType = null;
            if (this.elementTypeOid != 0) {
                elementType = this.typeRegistry.get(this.elementTypeOid);
            }

            return new PostgresType(
                    this.name,
                    this.oid,
                    this.jdbcId,
                    this.modifiers,
                    this.typeInfo,
                    this.enumValues,
                    parentType,
                    elementType);
        }
    }
}
